﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace Asol.Reporting.Support
{
    /// <summary>
    /// Messages: kontejner na texty zpráv
    /// </summary>
    public class Messages
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor pro implicitní soubor zpráv.
        /// To je soubor (aplikace)/ReportingMessages.xml
        /// Zadané jméno může/nemusí obsahovat adresář (absolutní/relativní), základnou k souboru je adresář aplikace.
        /// </summary>
        internal Messages()
        {
            this._TextDict = null;
            this._CodeDict = null;
            this._FileName = null;
        }
        /// <summary>
        /// Konstruktor pro explicitně zadaný soubor zpráv.
        /// Zadané jméno může/nemusí obsahovat adresář (absolutní/relativní), základnou k souboru je adresář aplikace.
        /// </summary>
        /// <param name="fileName"></param>
        internal static Messages FromFile(string fileName)
        {
            Messages messages = new Messages();
            messages._FileName = fileName;
            return messages;
        }
        /// <summary>
        /// Konstruktor pro explicitně zadaný obsah zpráv, pokud jsou zprávy načteny například z databáze.
        /// </summary>
        /// <param name="fileName"></param>
        internal static Messages FromContent(string content)
        {
            Messages messages = new Messages();
            messages.LoadMessagesFromContent(content);
            return messages;
        }
        /// <summary>
        /// Dictionary uspořádaná podle čitelného textu primárního jazyka. Hodnotou je MessageItem = soubor překladů tohoto výrazu do dalších jazyků.
        /// </summary>
        protected Dictionary<string, MessageItem> TextDict { get { this.LoadMessages(false); return this._TextDict; } }
        /// <summary>
        /// Dictionary uspořádaná podle Code překladu z messages.xml. Hodnotou je MessageItem = soubor překladů tohoto výrazu do dalších jazyků.
        /// </summary>
        protected Dictionary<string, MessageItem> CodeDict { get { this.LoadMessages(false); return this._CodeDict; } }
        /// <summary>Slovník, kde klíčem je čitelný text primárního jazyka</summary>
        private Dictionary<string, MessageItem> _TextDict;
        /// <summary>Slovník, kde klíčem je Code překladu z messages.xml</summary>
        private Dictionary<string, MessageItem> _CodeDict;
        private string _FileName;
        #endregion
        #region Vyhledání a přidávání dat
        /// <summary>
        /// Najde a vrátí text odpovídající zadanému textu ve výchozím jazyce kódu pro daný překlad
        /// </summary>
        /// <param name="message"></param>
        /// <param name="language"></param>
        /// <returns></returns>
        public string GetText(string message, string language)
        {
            if (String.IsNullOrEmpty(message)) return message;

            MessageItem item;
            if (this.CodeDict.TryGetValue(message, out item) || this.TextDict.TryGetValue(message, out item))
                return item.GetText(language);

            // Přidat novou položku, jen do slovníku TextDict:
            item = new MessageItem("CZ", message);
            this.TextDict.Add(message, item);

            return message;
        }
        /// <summary>
        /// true pokud this obsahuje nějaké nově přidané položky
        /// </summary>
        public bool ContainsChange { get { return (this._CodeDict != null && this._TextDict != null && this._TextDict.Count > this._CodeDict.Count); } }
        #endregion
        #region Load, Save
        /// <summary>
        /// Fullname souboru XML pro Load/Save (messages.xml)
        /// </summary>
        public string FileName { get { return Assistant.GetFileNameOnPath(Assistant.PathType.ExecutablePath, this._FileName); } }
        /// <summary>
        /// Vytvoří slovník zpráv, načte jej ze souboru this.FileName (pokud existuje), a uloží do this.
        /// </summary>
        private void LoadMessages(bool force)
        {
            if (!force && this._TextDict != null && this._CodeDict != null) return;
            this.LoadMessagesFromFile(this.FileName);
        }
        /// <summary>
        /// Vytvoří slovník zpráv, načte jej ze souboru this.FileName (pokud existuje), a uloží do this._TextDict a this._CodeDict
        /// </summary>
        private void LoadMessagesFromFile(string fileName)
        {
            if (String.IsNullOrEmpty(fileName) || !System.IO.File.Exists(fileName))
                return;

            XmlDoc xDoc = XmlDoc.CreateFromFile(fileName);

            this.LoadMessagesFromXmlDoc(xDoc);
        }
        /// <summary>
        /// Načte obsah z dodaného XML stringu
        /// </summary>
        /// <param name="content"></param>
        private void LoadMessagesFromContent(string content)
        {
            if (String.IsNullOrEmpty(content))
                this.LoadMessagesFromXmlDoc(null);
            else
            {
                XmlDoc xDoc = new XmlDoc();
                xDoc.LoadXml(content);
                this.LoadMessagesFromXmlDoc(xDoc);
            }
        }
        private void LoadMessagesFromXmlDoc(XmlDoc xDoc)
        {
            Dictionary<string, MessageItem> textDict = new Dictionary<string, MessageItem>();
            Dictionary<string, MessageItem> codeDict = new Dictionary<string, MessageItem>();

            if (xDoc != null)
            {
                foreach (var element in xDoc.RootElement.ChildNodes)
                {
                    MessageItem item = MessageItem.CreateFromXml(element as XmlElement);
                    if (item != null)
                    {
                        if (!textDict.ContainsKey(item.MsgKey))
                            textDict.Add(item.MsgKey, item);
                        if (!codeDict.ContainsKey(item.MsgKey))
                            codeDict.Add(item.MsgCode, item);
                    }
                }
            }

            this._TextDict = textDict;
            this._CodeDict = codeDict;
        }
        /// <summary>
        /// Uloží hlášky do souboru. Tato metoda může hrát roli EventHandleru.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        internal void SaveMessages(object sender, EventArgs args)
        {
            this.SaveMessages();
        }
        /// <summary>
        /// Uloží hlášky do souboru
        /// </summary>
        public void SaveMessages()
        {
            if (this._TextDict == null) return;             // Nebylo použito = tedy nebylo ani načteno => nebude ukládáno.
            if (!Assistant.IsProgrammer) return;            // Ukládá se jen pro programátora
            if (!this.ContainsChange) return;               // Není nic nového

            XmlDoc xDoc = new XmlDoc(XML_ELEMENT_ROOT);
            xDoc.AddSchema("http://tempuri.org/Msg.xsd", "xmlns");

            xDoc.AddAttributeTo(xDoc.RootElement, "Version", "1.00");
            xDoc.AddAttributeTo(xDoc.RootElement, "Created", Convertor.DateTimeToString(DateTime.Now));

            List<MessageItem> messageItems = this.PrepareMessagesForSave();
            foreach (MessageItem messageItem in messageItems)
                messageItem.SaveTo(xDoc, xDoc.RootElement);

            xDoc.Save(this.FileName, true);
        }
        /// <summary>
        /// Vytvoří seznam textů pro uložení (vloží položky, které již pocházejí z uloženého souboru, a přidá nové položky které před tím očísluje).
        /// </summary>
        /// <returns></returns>
        protected List<MessageItem> PrepareMessagesForSave()
        {
            List<MessageItem> result = new List<MessageItem>();

            // 1) Do resultu dám položky, které nejsou nové, a setřídím je podle MsgCode vzestupně:
            result.AddRange(this._TextDict.Values.Where(i => !i.IsNew));
            result.Sort((a, b) => a.MsgId.CompareTo(b.MsgId));

            // 2) Určím nejvyšší číslo MsgCode:
            int lastId = (result.Count == 0 ? 0 : result[result.Count - 1].MsgId);

            // 3) Postupně přidám do resultu nově vytvořené položky, přitom je očísluji:
            foreach (MessageItem item in this._TextDict.Values.Where(i => i.IsNew))
            {
                item.CreateNewId(ref lastId);
                result.Add(item);
            }

            return result;
        }
        /// <summary>"messages"</summary>
        internal const string XML_ELEMENT_ROOT = "messages";
        /// <summary>"mess"</summary>
        internal const string XML_ELEMENT_MESSAGE = "mess";
        /// <summary>"Code"</summary>
        internal const string XML_ATTRIBUTE_CODE = "Code";
        /// <summary>"text"</summary>
        internal const string XML_ELEMENT_TEXT = "text";
        /// <summary>"LangID"</summary>
        internal const string XML_ATTRIBUTE_LANG = "LangID";
        /// <summary>"Value"</summary>
        internal const string XML_ATTRIBUTE_VALUE = "Value";
        /// <summary>"Help"</summary>
        internal const string XML_ATTRIBUTE_HELP = "Help";
        /// <summary>"Tooltip"</summary>
        internal const string XML_ATTRIBUTE_TOOLTIP = "Tooltip";
        #endregion
    }
    #region class MessageItem : jedna zpráva. class MessageText : jeden text + tooltip v jednom jazyce.
    /// <summary>
    /// Texty jedné zprávy. Klíčem (MsgKey) je text zprávy v jazyce programu (čeština), hodnotou je sada odpovídajících přeložených textů do daších jazyků.
    /// </summary>
    public class MessageItem
    {
        #region Konstrukce a property
        /// <summary>
        /// Konstruktor volaný při Load
        /// </summary>
        /// <param name="msgCode"></param>
        public MessageItem(string msgCode)
        {
            this.TextDict = new Dictionary<string, MessageText>();
            if (!String.IsNullOrEmpty(msgCode) && msgCode.Length >= 9 && msgCode.Substring(0, 3) == "Msg")
            {
                int id;
                if (Int32.TryParse(msgCode.Substring(3), out id) && id > 0)
                    this.MsgId = id;
            }
        }
        /// <summary>
        /// Konstruktor volaný při InsertNew
        /// </summary>
        /// <param name="language"></param>
        /// <param name="text"></param>
        public MessageItem(string language, string text)
        {
            this._KeyLanguage = language;
            this.MsgId = 0;
            this.TextDict = new Dictionary<string, MessageText>();
            this.TextDict.Add(language, new MessageText(language, text, null));
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.MsgCode + ": " + this.MsgKey;
        }
        /// <summary>
        /// Klíč zprávy = text nalezený v programu. Ve výchozím stavu je to tedy čitelný text v jazyce CZ.
        /// V programu se používají čitelné české texty, překlad se provádí z jazyka CZ (=MsgKey) přes Messages do cílového jazyka.
        /// Při překladu textu, který systém ještě nezná, se založí nový záznam. Při ukončování aplikace se uloží kompletní rozšířený soubor textů.
        /// </summary>
        public string MsgKey { get { return (!String.IsNullOrEmpty(this._KeyLanguage) && this.TextDict.ContainsKey(this._KeyLanguage) ? this.TextDict[this._KeyLanguage].Text : ""); } }
        /// <summary>
        /// Id zprávy = pořadové číslo (zde Int32), zapsané do Messages.xml formátované na string, typicky "Msg000012".
        /// Pokud je 0, pak jde o nový záznam, který ještě není uložen v souboru.
        /// </summary>
        public int MsgId { get; private set; }
        /// <summary>
        /// Kód zprávy = string například "Msg012345"
        /// </summary>
        public string MsgCode { get { return "Msg" + this.MsgId.ToString("000000"); } }
        /// <summary>
        /// Dictionary uspořádaná podle klíče = jazyk, hodnota = odpovídající texty v daném jazyce (text, tooltip).
        /// </summary>
        public Dictionary<string, MessageText> TextDict { get; private set; }
        /// <summary>
        /// true = tato položka je nově přidaná a musí se uložit do souboru překladů.
        /// </summary>
        public bool IsNew { get { return (this.MsgId == 0); } }
        /// <summary>
        /// Klíčový jazyk položky: je to CZ pro nově zakládané položky, a pro položky je to CZ nebo EN pokud existuje, nebo první existující.
        /// </summary>
        private string _KeyLanguage;
        #endregion
        #region Vyhledání a přidávání dat
        /// <summary>
        /// Najde a vrátí text pro daný překlad
        /// </summary>
        /// <param name="language"></param>
        /// <returns></returns>
        public string GetText(string language)
        {
            if (String.IsNullOrEmpty(language) || this.TextDict.Count == 0) return this.MsgKey;

            MessageText text;
            if (this.TextDict.TryGetValue(language, out text) ||
                this.TextDict.TryGetValue("CZ", out text) ||
                this.TextDict.TryGetValue("EN", out text) ||
                this.TextDict.TryGetValue("SK", out text))
                return text.Text;
            
            return this.MsgKey;
        }
        #endregion
        #region Load, Save
        /// <summary>
        /// Vytvoří objekt MessageItem a naplní jej daty z dodaného XML elementu. Vrátí naplněný objekt.
        /// Pokud je dodán vadný XML element, pak vrátí NULL.
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        internal static MessageItem CreateFromXml(XmlElement element)
        {
            if (element == null) return null;
            if (element.LocalName != Messages.XML_ELEMENT_MESSAGE) return null;       // <mess Code="Msg012345">

            string msgCode = element.GetAttribute(Messages.XML_ATTRIBUTE_CODE);
            if (String.IsNullOrEmpty(msgCode)) return null;

            MessageItem item = new MessageItem(msgCode);
            if (item.IsNew) return null;

            foreach (var subElement in element.ChildNodes)
            {
                MessageText text = MessageText.CreateFromXml(subElement as XmlElement);
                if (text != null && !item.TextDict.ContainsKey(text.Language))
                    item.TextDict.Add(text.Language, text);
            }
            if (item.TextDict.Count == 0) return null;

            item._DetectKeyLanguage();
            return item;
        }
        /// <summary>
        /// Nastaví si klíčový jazyk (this._KeyLanguage), podle něhož se určuje textový klíč this.MsgKey.
        /// Je to první z jazyků CZ, EN, SK; pro který objekt obsahuje položku s textem.
        /// </summary>
        private void _DetectKeyLanguage()
        {
            if (this.TextDict.Count == 0) this._KeyLanguage = "CZ";
            else if (this.TextDict.ContainsKey("CZ") && this.TextDict["CZ"].HasText) this._KeyLanguage = "CZ";
            else if (this.TextDict.ContainsKey("EN") && this.TextDict["EN"].HasText) this._KeyLanguage = "EN";
            else if (this.TextDict.ContainsKey("SK") && this.TextDict["SK"].HasText) this._KeyLanguage = "SK";
            else this._KeyLanguage = this.TextDict.Values.First().Language;
        }
        /// <summary>
        /// Nastaví svoje MsgId = ++lastId;
        /// </summary>
        /// <param name="lastId"></param>
        internal void CreateNewId(ref int lastId)
        {
            if (this.MsgId == 0 && lastId >= 0)
                this.MsgId = ++lastId;
        }
        /// <summary>
        /// Uloží sebe jako nový element do daného elementu
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="parentElement"></param>
        internal void SaveTo(XmlDoc xDoc, XmlElement parentElement)
        {
            XmlElement element = xDoc.CreateElementIn(parentElement, Messages.XML_ELEMENT_MESSAGE);
            xDoc.AddAttributeTo(element, Messages.XML_ATTRIBUTE_CODE, this.MsgCode);

            List<MessageText> textList = this.PrepareTextsForSave();
            foreach (MessageText text in textList)
                text.SaveTo(xDoc, element);
        }
        /// <summary>
        /// Vrátí soupis textů k uložení
        /// </summary>
        /// <returns></returns>
        private List<MessageText> PrepareTextsForSave()
        {
            List<MessageText> result = new List<MessageText>(this.TextDict.Values);
            if (result.Count > 1)
                result.Sort(MessageText.CompareByLanguage);
            return result;
        }
        #endregion
    }
    /// <summary>
    /// Třída popisující jeden text (jazyk, text, tooltip).
    /// </summary>
    public class MessageText
    {
        #region Konstrukce a property
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="language"></param>
        /// <param name="text"></param>
        /// <param name="toolTip"></param>
        public MessageText(string language, string text, string toolTip)
        {
            string lng = language.ToUpper();
            this.Language = lng;
            this._SortLanguage = (String.IsNullOrEmpty(lng) ? "9" : (lng == "CZ" ? "1" : (lng == "EN" ? "2" : (lng == "SK" ? "3" : "4")))) + lng;
            this.Text = text;
            this.ToolTip = toolTip;
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Language + ": " + this.Text;
        }
        /// <summary>Jazyk textu</summary>
        public string Language { get; private set; }
        /// <summary>Pořadí jazyka + Jazyk textu</summary>
        private string _SortLanguage;
        /// <summary>Text</summary>
        public string Text { get; private set; }
        /// <summary>Tooltip k textu</summary>
        public string ToolTip { get; private set; }
        /// <summary>true pokud položka obsahuje neprázdný text</summary>
        public bool HasText { get { return !String.IsNullOrEmpty(this.Text); } }
        #endregion
        #region Load, Save
        /// <summary>
        /// Komparátor podle pořadí jazyka (CZ=1, EN=2, SK=3, ostatní=4+podle abecedy)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByLanguage(MessageText a, MessageText b)
        {
            return a._SortLanguage.CompareTo(b._SortLanguage);
        }
        /// <summary>
        /// Vytvoří objekt MessageItem a naplní jej daty z dodaného XML elementu. Vrátí naplněný objekt.
        /// Pokud je dodán vadný XML element, pak vrátí NULL.
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        internal static MessageText CreateFromXml(XmlElement element)
        {
            if (element == null) return null;
            if (element.LocalName != Messages.XML_ELEMENT_TEXT) return null;       // <text LangID="CZ" Value="Příprava dokladu k napočtení dat" Tooltip="Nápověda jako Brno" />

            string language = element.GetAttribute(Messages.XML_ATTRIBUTE_LANG);
            string text = element.GetAttribute(Messages.XML_ATTRIBUTE_VALUE);
            string toolTip = element.GetAttribute(Messages.XML_ATTRIBUTE_TOOLTIP);
            if (String.IsNullOrEmpty(language) || String.IsNullOrEmpty(text)) return null;

            return new MessageText(language, text, toolTip);
        }
        /// <summary>
        /// Uloží sebe jako nový element do daného elementu
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="parentElement"></param>
        internal void SaveTo(XmlDoc xDoc, XmlElement parentElement)
        {
            XmlElement element = xDoc.CreateElementIn(parentElement, Messages.XML_ELEMENT_TEXT);
            xDoc.AddAttributeTo(element, Messages.XML_ATTRIBUTE_LANG, this.Language);
            xDoc.AddAttributeTo(element, Messages.XML_ATTRIBUTE_VALUE, this.Text);
            if (!String.IsNullOrEmpty(this.ToolTip))
                xDoc.AddAttributeTo(element, Messages.XML_ATTRIBUTE_TOOLTIP, this.ToolTip);
        }
        #endregion
    }
    #endregion
    #region Custom atributy popisující třídy a jejich členy
    /// <summary>
    /// Atribut popisující text a info o členu
    /// </summary>
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class DisplayTextInfoAttribute : Attribute
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="text"></param>
        public DisplayTextInfoAttribute(string text)
        {
            this.Text = text;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="text"></param>
        /// <param name="info"></param>
        public DisplayTextInfoAttribute(string text, string info)
        {
            this.Text = text;
            this.Info = info;
        }
        /// <summary>
        /// Text (název, label) zadaný v kódu. Před zobrazením projde lokalizací pomocí Assistant.GetMessage(Text).
        /// </summary>
        public string Text { get; private set; }
        /// <summary>
        /// Informace (tooltip) zadaná v kódu. Před zobrazením projde lokalizací pomocí Assistant.GetMessage(Info).
        /// </summary>
        public string Info { get; private set; }
    }
    #endregion
}
